package NajaTaskClient.Conexao;

//~--- JDK imports ------------------------------------------------------------
import com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException;
import java.sql.*;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author NidorX
 */
public final class Db {

    private static Db instancia;
    String driver = "com.mysql.jdbc.Driver";
    Connection conexao;
    int conexaoStatus;
    String nome;
    ResultSet resultSet;
    String senha;
    Statement statement;
    Set<DbTabela> tabelas;
    String url;
    String usuario;

    /**
     *
     * @param config
     */
    private Db() {
        this.tabelas = new TreeSet<DbTabela>();
    }

    /**
     * retorna a instancia de dbqueries
     * @return DbQueries
     */
    public static synchronized Db get() {
        if (instancia == null) {
            instancia = new Db();
        }

        return instancia;
    }

    /**
     * Obtem o nome do banco de dados
     * @return String
     */
    public String obterNome() {
        return this.nome;
    }

    /**
     *
     * @return
     */
    public String informacoes() {
        String info = "Nome do banco de dados : " + this.obterNome()
                + "\nTabelas existentes: " + this.obterNomeTabelas();
        return info;
    }

    public void conexaoConfig(String host, String porta, String usuario, String senha, String bancoDeDados) {
        this.usuario = usuario;
        this.senha = senha;
        this.nome = bancoDeDados;
        this.url = "jdbc:mysql://" + host + ":" + porta;
        System.out.println(this.url);
    }

    /**
     * fecha a conexao com o banco de dados
     * @return true ou false
     */
    public boolean conexaoFechar() {
        try {
            this.conexaoObter().close();

            return true;
        } catch (SQLException e) {
            return false;
        }
    }

    /**
     * reinicia a conexao com o banco de dados
     * @return java.sql.Connection ou null
     */
    public java.sql.Connection conexaoReiniciar() {
        this.conexaoFechar();
        return this.conexaoObter();
    }

    /**
     * Faz uma conexao com o banco de dados .
     * @return true ou false
     * @throws SQLException
     */
    public boolean conectar() throws SQLException {
        if (this.conexaoObter() != null) {
            this.criarBDTabelasEColunas();
            // System.out.println("Conexao estabelecida com o banco de dados: \n" + "" + this.informacoes());
            // this.imprimeTabelas();
            return true;
        } else {
            return false;
        }
    }

    /**
     * obtem a conexao com o banco de dados
     * @return java.sql.Connection ou null
     */
    private java.sql.Connection conexaoObter() {
        try {
//          Carregando o JDBC Driver padrão
            Class.forName(this.driver);
//          Configurando a nossa conexão com um banco de dados//
            this.conexao = DriverManager.getConnection(this.url, this.usuario, this.senha);
            this.statement = this.conexao.createStatement();
            // this.dbCriar(Db.get().obterNome());

            return this.conexao;
        } catch (ClassNotFoundException e) {    // Driver não encontrado
            e.printStackTrace();
            // sistemaDeLog.getInstance().info(2, 0, " Arquivo DbConexao.java \n " + e);
            return null;
        } catch (SQLException e) {              // Não conseguindo se conectar ao banco
            e.printStackTrace();
            //sistemaDeLog.getInstance().info(2, 1, " Arquivo DbConexao.java \n " + e);

            return null;
        }



    }

    /**
     * testa se existe o banco de dados especificado no GBD
     * @param dbNome
     * @return true ou false
     * @throws SQLException
     */
    public boolean dbExiste(String dbNome) throws SQLException {
        ResultSet rst = this.statement.executeQuery(DbQueries.getInstance().showDbs());

        while (rst.next()) {
            if (rst.getString("database").equals(dbNome)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Cria um banco de dados no SGDB
     * @param dbNome
     * @return true ou false
     * @throws SQLException
     */
    private boolean dbCriar(String dbNome) throws SQLException {
        if (!this.dbExiste(dbNome)) {
            return this.statement.execute(DbQueries.getInstance().createDb(dbNome));
        }

        return false;
    }

    /**
     * Seleciona um banco de dados
     * @param dbNome
     * @return true ou false
     * @throws SQLException
     * @throws ExcecaoGenericaMensagem 
     */
    public boolean dbSelecionar(String dbNome) throws SQLException, ExcecaoGenericaMensagem {
        if (!this.dbExiste(dbNome)) {
            throw new ExcecaoGenericaMensagem("O banco de dados \"" + dbNome + "\" nao existe ."
                    + "Verifique se o sistema já foi instalado ");
        }
        return this.statement.execute(DbQueries.getInstance().useDb(dbNome));
    }

    /**
     * Teste se determinada tabela existe no banco de dados .
     * @param tabela
     * @return true ou false
     * @throws SQLException
     */
    public boolean dbTabelaExiste(String tabela) throws SQLException {
        ResultSet rst = this.statement.executeQuery(DbQueries.getInstance().showTbs());
        if (rst != null) {
            while (rst.next()) {
                if (rst.getString("Tables_in_" + this.obterNome()).toLowerCase().equals(tabela.toLowerCase())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * adiciona uma nova tabela a lista de tabelas e retorna o ponteiro para essa
     * tabela , de forma que pode se usar a mesma tabela em varios lugares diferentes.
     * @param tabela
     * @return true ou false
     * @throws ExcecaoGenericaMensagem 
     */
    public DbTabela dbTabelaAdicionar(DbTabela tabela) throws ExcecaoGenericaMensagem {
        if (!this.tabelas.add(tabela)) {
            // se nao conseguiu adicionar a tabela , verificar se já existe alguma
            // tabela adicionada com esse nome
            for (DbTabela e : this.tabelas) {
                if (e.obterNome().equals(tabela.obterNome())) {
                    return e;
                }
            }
            throw new ExcecaoGenericaMensagem("Nao foi possivel adicionar a nova tabela , um erro desconhecido");
        } else {
            return tabela;
        }

    }

    /**
     * Insere no banco de dados os valores especificados na tabela especificada
     * @param tabela nome da tabela
     * @param chaveValor
     * @return true ou false
     * @throws SQLException
     */
    public boolean dbTabelaInsert(String tabela, Map<String, String> chaveValor) throws SQLException {
        if (this.dbTabelaExiste(tabela)) {
            Set<Map.Entry<String, String>> set = chaveValor.entrySet();
            Iterator<Map.Entry<String, String>> it = set.iterator();

            try {
                // criando a string sql para insert
                String valores = new String();
                String colunas = new String();
                it = set.iterator();
                while (it.hasNext()) {
                    Map.Entry<String, String> me = it.next();

                    colunas += me.getKey() + ",";
                    valores += "'" + me.getValue() + "',";
                }
                // removendo o "'" último
                colunas = colunas.substring(0, colunas.length() - 1);
                valores = valores.substring(0, valores.length() - 1);

                // executando a query de insert
                return this.statement.execute(DbQueries.getInstance().insert(tabela, colunas, valores));
            } catch (MySQLIntegrityConstraintViolationException e) {    // Chave duplicada
                // se for duplicado , update
                if (e.getMessage().indexOf("Duplicate entry") >= 0) {
                    //return this.statement.execute(DbQueries.getInstance().update(tabela, colunas, valores));
                }

                return false;
            }
        }

        return false;
    }

    /**
     * Insere no banco de dados os valores especificados na tabela especificada
     * @param tabela nome da tabela
     * @param colunas exemplo : nome,codigo,valor
     * @param valores exemplo : 'alex rodin', '125','36.25'
     * @return true ou false
     * @throws SQLException
     */
    public boolean dbTabelaUpdate(String tabela, String colunas, String valores) throws SQLException {
        if (this.dbTabelaExiste(tabela)) {

            // DbQueries.get().insert(tabela, colunas, valores);
            try {
                return this.statement.execute(DbQueries.getInstance().insert(tabela, colunas, valores));
            } catch (MySQLIntegrityConstraintViolationException e) {    // Chave duplicada
                //sistemaDeLog.getInstance().info(0, 0, e.getMessage());

                // se for duplicado , update
                if (e.getMessage().indexOf("Duplicate entry") >= 0) {
                    // sistemaDeLog.getInstance().info(0, 0, e.getMessage());

                    return this.statement.execute(DbQueries.getInstance().update(tabela, colunas, valores));
                }

                return false;
            }
        }

        return false;
    }

    /**
     * Cria uma tabela no banco de dados 
     * @param obterNome
     */
    private boolean dbTabelaCriar(DbTabela tabela) throws SQLException, ExcecaoGenericaMensagem {
        String colunas = "";
        // criando uma string com a lista das colunas 
        for (DbTabelaColuna col : tabela.obterColunas()) {
            colunas += col.obterStringSql();
            colunas += ",";
        }
        //remover o "," final
        colunas = colunas.substring(0, colunas.length() - 1);
        System.out.println("Criando tabela \"" + tabela.obterNome() + " \" no banco de dados \"" + this.obterNome() + "\"");
        System.out.println(DbQueries.getInstance().createTable(tabela.obterNome(), colunas));

        this.statement.execute(DbQueries.getInstance().createTable(tabela.obterNome(), colunas));
        System.out.println("Tabela \"" + tabela.obterNome() + " \" criada com sucesso no banco de dados \"" + this.obterNome() + "\"");

        return true;

    }

    /**
     * Teste se determinada coluna existe na tabela do Banco de dados .
     * @param tabela
     * @param coluna 
     * @return true ou false
     * @throws SQLException
     */
    public boolean dbTabelaColunaExiste(String tabela, String coluna) throws SQLException {
        ResultSet rst = this.statement.executeQuery(DbQueries.getInstance().showCols(tabela));
        while (rst.next()) {
            if (rst.getString("Field").toLowerCase().equals(coluna.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Consulta o banco de dados e verifica se já existe o valor na coluna da tabela
     * @param tabela 
     * @param coluna
     * @param valor
     * @return
     */
    public boolean dbTabelaColunaExisteValor(String tabela, String coluna, String valor) {
        ResultSet rst = null;
        try {
            rst = this.statement.executeQuery(DbQueries.getInstance().selectWhereSimple(tabela, coluna, valor));
            if (rst.last()) {
                System.out.println(DbQueries.getInstance().selectWhereSimple(tabela, coluna, valor));
                return true;
            }
        } catch (SQLException ex) {
            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
        }

        return false;
    }

    /**
     * Remove de 'tabela' a linha em que a clausula satisfaça
     * @param tabela
     * @param clausula
     * @return
     */
    public boolean dbTabelaColunaRemoveLinhaWhereClausula(String tabela, String clausula) {
        try {
            this.statement.execute(DbQueries.getInstance().deleteFromWhereColumn(tabela, clausula));
            return true;

        } catch (SQLException ex) {

            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    /**
     * Atualiza tabela com os valores filtrando pelas clausulas where
     * @param tabela nome da tabela
     * @param valores 
     * @param clausula nome='alex' AND codigo='25'
     * @return
     */
    //updateWhere(String tabela, String valores,String clausula)
    public boolean dbTabelaColunaAtualizaLinhaClausulaWhere(String tabela, String valores, String clausula) {
        try {
            this.statement.execute(DbQueries.getInstance().updateWhere(tabela, valores, clausula));
        } catch (SQLException ex) {
            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;

    }

    /**
     * Consulta o banco de dados e verifica se já existe o valor na coluna da tabela
     * @param tabela
     * @param coluna
     * @param valor
     * @return
     */
    public ResultSet dbTabelaSelectAll(String tabela) {
        ResultSet rst = null;
        try {
            rst = this.statement.executeQuery(DbQueries.getInstance().selectAll(tabela));

        } catch (SQLException ex) {
            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
        }
        return rst;
    }

     /**
     * Consulta o banco de dados e verifica se já existe o valor na coluna da tabela
     * @param tabela
     * @param coluna
     * @param valor
     * @return
     */
    public ResultSet dbTabelaSelectAllWhere(String tabela,String clausula) {
        ResultSet rst = null;
        try {
            rst = this.statement.executeQuery(DbQueries.getInstance().selectWhere(tabela, clausula));

        } catch (SQLException ex) {
            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
        }
        return rst;
    }

    /**
     * Consulta o banco de dados e verifica se já existe o valor na coluna da tabela
     * @param tabela
     * @param coluna
     * @param valor
     * @return
     */
    public Vector<String> dbTabelaColunaObterValor(String tabela, String coluna, String valor) {
        ResultSet rst = null;
        Vector<String> valores = new Vector<String>();
        try {
            rst = this.statement.executeQuery(DbQueries.getInstance().selectWhereSimplePerson(tabela, coluna, valor));
            while (rst.next()) {
                valores.add(rst.getString(coluna));
            }

        } catch (SQLException ex) {
            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
        }

        return valores;
    }

    /**
     * Cria no banco de dados uma coluna na tabela especificada
     * @param tabela
     * @param colunaSQLCreateString 
     * @return true ou false
     * @throws SQLException
     */
    public boolean dbTabelaColunaCriar(String tabela, String colunaSQLCreateString) throws SQLException {
        return this.statement.execute(DbQueries.getInstance().createColumn(tabela, colunaSQLCreateString));

    }

    /**
     * Checa no banco de dados se determinada coluna tem a mesma estrutura que
     * o objeto em questao . Checa itens como TIPO , TAMANHO , AUTOINCREMENTA ,
     * E ETC
     * @param col
     */
    private void dbTabelaColunaChecarEstrutura(String tabela, DbTabelaColuna col) throws SQLException, ExcecaoGenericaMensagem {
        ResultSet rst = this.statement.executeQuery(DbQueries.getInstance().showCols(tabela));
        String coluna = col.obterNome();
        while (rst.next()) {
            if (rst.getString("Field").equals(coluna)) {
                /*
                 * testar os dados do objeto col com os dados adquiridos do servidor
                 */
                String bdTypeSt = rst.getString("Type");//tipo ex: int(11) ou decimal(11,0)
                String bdNullSt = rst.getString("Null");//null nonull
                String bdPrimSt = rst.getString("Key");//primary
                String bdAutoIncSt = rst.getString("Extra");//primary
                //

                // tratando o tipo de dado
                String[] bdTypeArr = bdTypeSt.split("\\(");
                String bdType = bdTypeArr[0];
                if (!bdType.toUpperCase().equals(col.obterTipo())) {
                    throw new ExcecaoGenericaMensagem("Tipo de dado da coluna \"" + coluna + "\" diferente do servidor");
                }
                // tratando o tamanho do dado
                String[] bdTamArr = null;
                if (bdTypeArr.length > 1) {
                    bdTamArr = bdTypeArr[1].split("\\)");
                    if (col.obterTipo().equals("INT") || col.obterTipo().equals("DECIMAL") || col.obterTipo().equals("VARCHAR")) {
                        if (!Integer.toString(col.obterTamanho()).equals(bdTamArr[0])) {
                            col.setarTamanho(Integer.parseInt(bdTamArr[0]));
                        }

                    }
                }


                // NULL | NOT NULL
                Boolean bdNullbol = (bdNullSt.toUpperCase().equals("YES")) ? true : false;
                if (bdNullbol != col.obterNulo()) {
                    throw new ExcecaoGenericaMensagem("Tipo de dado da coluna \"" + coluna + "\" diferente do servidor . NULL | NOT NULL");

                }

                // PRIMARY
                Boolean bdPrimbol = (bdPrimSt.toUpperCase().equals("PRI")) ? true : false;
                if (bdPrimbol != col.obterChavePrimaria()) {
                    throw new ExcecaoGenericaMensagem("Tipo de dado da coluna \"" + coluna + "\" diferente do servidor . CHAVE PRIMARIA");

                }

                // AUTO INCREMENT
                Boolean bdAutoIncbol = (bdAutoIncSt.toLowerCase().equals("auto_increment")) ? true : false;
                if (bdAutoIncbol != col.obterAutoIncrementa()) {
                    throw new ExcecaoGenericaMensagem("Tipo de dado da coluna \"" + coluna + "\" diferente do servidor . AUTOINCREMENT");

                }




            }
        }
    }

    /**
     * Imprime o conjunto de colunas da tabela atual
     */
    public void imprimeTabelas() {
        for (DbTabela e : this.tabelas) {
            e.exibetDados();
        }
    }

    /**
     * 
     * @return
     */
    public String obterNomeTabelas() {
        String tabs = "";
        for (DbTabela e : this.tabelas) {
            tabs += e.obterNome() + ",";
            //System.out.print(e.obterNome());
            //e.exibeDados();
        }
        return tabs;

    }

    /**
     * cria todos os elementos necessários para funcionar
     *
     */
    public void criarBDTabelasEColunas() {
        try {
            /*
             * INSTALANDO/CONFIGURANDO/VERIFICANDO O BANCO DE DADOS DO SISTEMA
             * 1º BANCO DE DADOS (BD)
             * 2º TABELAS
             * 3º COLUNAS
             *
             * LÓGICA:
             *
             *
             * SE 'BD' NAO EXISTE ENTAO :
             * |     CRIA 'BD'
             * FIM-SE
             *
             * PARA CADA 'TABELA' DE 'BD' COMO 'TAB' , FAÇA:
             * |   SE 'TAB' EXISTE , ENTAO:
             * |    |   PARA CADA 'COLUNA' DE 'TAB' COMO 'COL' , FAÇA:
             * |    |   |   SE 'COL' NAO EXISTE , ENTAO:
             * |    |   |   |   SE NAO CONSEGUIR CRIAR 'COL' , ENTAO:
             * |    |   |   |   |   !ERRO - na criação de 'COL'
             * |    |   |   |   FIM-SE
             * |    |   |   FIM-SE
             * |    |   |   SENAO
             * |    |   |   |   SE ESTRUTURA DE 'COL' ESTÁ ERRADA , ENTAO:
             * |    |   |   |   |   !ERRO - estrutura de 'COL' errada
             * |    |   |   |   FIM-SE
             * |    |   |   FIM-SENAO
             * |    |   FIM-PARA
             * |    FIM-SE
             * |    SENAO
             * |    |   SE CONSEGUIR CRIAR 'TAB' , ENTAO:
             * |    |   |   PARA CADA 'COLUNA' DE 'TAB' COMO 'COL' , FAÇA:
             * |    |   |   |   SE NAO CONSEGUIR CRIAR 'COL' ENTAO:
             * |    |   |   |   |   !ERRO - na criaçao de 'COL'
             * |    |   |   |   FIM-SE
             * |    |   |   FIM-PARA
             * |    |   FIM-SE
             * |    |   SENAO
             * |    |   |   !ERRO - na criação de 'TAB'
             * |    |   FIM-SENAO
             * |    FIM-SENAO
             * FIM-PARA
             * RETORNA OK.
             *
             */
            // Se o banco de dados nao existe , deve ser criado
            if (!this.dbExiste(this.obterNome())) {
                try {
                    this.dbCriar(this.obterNome());
                    this.dbSelecionar(this.obterNome());

                } catch (SQLException ex) {
                    Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
                } catch (ExcecaoGenericaMensagem ex) {
                    Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else {
                try {
                    this.dbSelecionar(this.obterNome());
                } catch (ExcecaoGenericaMensagem ex) {
                    Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
                }
            }


            //* PARA CADA 'TABELA' DE 'BD' COMO 'TAB' , FAÇA:
            for (DbTabela tab : this.tabelas) {

                //*SE 'TAB' EXISTE , ENTAO:
                System.out.println(tab.obterNome());
                if (this.dbTabelaExiste(tab.obterNome())) {
                    //*PARA CADA 'COLUNA' DE 'TAB' COMO 'COL' , FAÇA:
                    for (DbTabelaColuna col : tab.obterColunas()) {
                        //SE 'COL' NAO EXISTE , ENTAO:
                        if (!this.dbTabelaColunaExiste(tab.obterNome(), col.obterNome())) {
                            //SE NAO CONSEGUIR CRIAR 'COL' , ENTAO: ERRO
                            this.dbTabelaColunaCriar(tab.obterNome(), col.obterStringSql());
                        } else {
                            try {
                                // *SE ESTRUTURA DE 'COL' ESTÁ ERRADA , ENTAO:
                                this.dbTabelaColunaChecarEstrutura(tab.obterNome(), col);
                            } catch (ExcecaoGenericaMensagem ex) {
                                Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
                            }


                        }
                    }
                } else {
                    try {
                        //SE CONSEGUIR CRIAR 'TAB' , ENTAO:
                        this.dbTabelaCriar(tab);
                        //*PARA CADA 'COLUNA' DE 'TAB' COMO 'COL' , FAÇA:
                           /* for (DbTabelaColuna col : tab.obterColunas()) {
                        //  CRIAR 'COL'
                        this.dbTabelaColunaCriar(tab.obterNome(), col.obterStringSql());
                        }*/

                    } catch (ExcecaoGenericaMensagem ex) {
                        Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }

            }
        } catch (SQLException ex) {
            Logger.getLogger(Db.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
